package com.zbs.framework.chat.websocket;

import com.alibaba.fastjson.JSONObject;
import com.zbs.framework.chat.constants.WebSocketConstants;
import com.zbs.framework.chat.entity.MessageBody;
import com.zbs.framework.chat.entity.RemoteMessage;
import com.zbs.framework.chat.service.ChatService;
import com.zbs.framework.common.redis.RedisUtil;
import com.zbs.framework.common.utils.BeanUtils;
import com.zbs.framework.security.config.FrameworkConstants;
import com.zbs.framework.security.entity.LoginUserInfo;
import com.zbs.framework.security.utils.TokenManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author websocket服务
 */
@ServerEndpoint(value = "/websocket/chat/{identifier}/{token}",subprotocols = {"protocol"})
@Component
@Slf4j
public class UserWebSocketServer {
    //静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
    /**
     * 记录当前在线连接数
     */
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("identifier") String identifier, @PathParam("token") String token) {
        if (!sessionMap.containsKey(identifier)) {
            addOnlineCount(); // 在线人数加1
            sessionMap.put(identifier, session);
            log.info("有新用户加入，identifier={}, 当前map大小：{}, 当前在线人数为：{}", identifier, sessionMap.size(), getOnlineCount());
            ChatService chatService = null;
            try {
                chatService = BeanUtils.getBean(ChatService.class);
            } catch (Exception e) {
                log.info("chatService 未实现");
            }
            sendMessage("连接成功", session);
            LoginUserInfo loginUser = null;
            if(FrameworkConstants.CHAT_IS_OPEN_TOKEN) {
                try {
                    loginUser = TokenManager.getLoginUser(token);
                } catch (Exception e) {
                    sendMessage(String.format("%s,服务端拒绝了你的连接",e.getMessage()), session);
                    try {
                        session.close();
                    } catch (IOException ex) {
                        log.info("错误信息：{}",ex.getMessage());
                    }
                }
            }
            if (chatService != null) {
                chatService.connectionSuccess(identifier,loginUser);
                chatService.onlineCountChange(getOnlineCount());
            }
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session, @PathParam("identifier") String identifier) {
        if (sessionMap.containsKey(identifier)) {
            sessionMap.remove(identifier);
            subOnlineCount();           //在线数减1
            log.info("有一连接关闭，移除identifier={}的用户session, 当前map大小：{}, 当前在线人数为：{}", identifier, sessionMap.size(), getOnlineCount());
            ChatService chatService = null;
            try {
                chatService = BeanUtils.getBean(ChatService.class);
            } catch (Exception e) {
                log.info("chatService 未实现");
            }
            if (chatService != null) {
                chatService.closeConnection(identifier);
                chatService.onlineCountChange(getOnlineCount());
            }
        }
    }

    /**
     * 收到客户端消息后调用的方法
     * 后台收到客户端发送过来的消息
     * onMessage 是一个消息的中转站
     * 接受 浏览器端 socket.send 发送过来的 json数据
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("identifier") String identifier) {
        log.info("服务端收到用户identifier={}的消息:{}", identifier, message);
        MessageBody messageBody;
        try {
            messageBody = JSONObject.parseObject(message, MessageBody.class);
            StringRedisTemplate stringRedisTemplate = null;
            try {
                stringRedisTemplate = BeanUtils.getBean(StringRedisTemplate.class);
            } catch (Exception e) {
                log.info("未集成redis");
            }
            RemoteMessage remoteMessage = new RemoteMessage(identifier, messageBody);

            ChatService chatService = null;
            try {
                chatService = BeanUtils.getBean(ChatService.class);
            } catch (Exception e) {
                log.info("chatService 未实现");
            }
            if (chatService != null) {
                chatService.sendMessage(remoteMessage.getIdentifier(), remoteMessage.getTo(), remoteMessage.getText(), false);
            }

            if (!sessionMap.containsKey(messageBody.getTo())) {
                // 如果当前session 不存在 ，说明在其他服务内
                if (stringRedisTemplate != null) {
                    log.info("消息走redis 发布订阅了......");
                    stringRedisTemplate.convertAndSend(WebSocketConstants.REDIS_CHANNEL,JSONObject.toJSONString(remoteMessage));
                }
            } else {
                // {"to": "admin", "text": "聊天文本"}
                this.sendMessage(remoteMessage);
            }
        } catch (Exception e) {
            log.info("消息体错误：{}",message);
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }


    /**
     * 服务端发送消息给客户端
     */
    public void sendMessage(String message, Session toSession) {
        try {
            log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }

    public void sendMessage(RemoteMessage message) {
        try {
            // {"to": "admin", "text": "聊天文本"}
            Session toSession = sessionMap.get(message.getTo()); // 根据 to用户名来获取 session，再通过session发送消息文本
            if (StringUtils.hasLength(message.getText())) {
                if (toSession != null) {
                    // 服务器端 再把消息组装一下，组装后的消息包含发送人和发送的文本内容
                    this.sendMessage(message.getText(), toSession);
                    log.info("发送给用户identifier={}，消息：{}", message.getTo(), message.getText());
                }
            }
        } catch (Exception e) {
            log.info("消息体错误：{}",message);
        }
    }

    /**
     * 服务端发送系统消息消息给客户端
     */
    public void sendMessage(String message, String identifier) {
        try {
            Session toSession = sessionMap.get(identifier);
            if (toSession != null) {
                log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
                toSession.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }

    /**
     * 服务端发送消息给所有客户端
     */
    public void sendAllMessage(String message) {
        try {
            for (Session session : sessionMap.values()) {
                log.info("服务端给客户端[{}]发送消息{}", session.getId(), message);
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }

    public static synchronized Long getOnlineCount() {
        // 优先redis 在线人数统计
        try {
            String s = RedisUtil.get(WebSocketConstants.onlineCountKey);
            return Long.parseLong(s);
        } catch (Exception e) {
            return WebSocketConstants.onlineCount;
        }
    }

    public static synchronized void addOnlineCount() {
        // 优先redis 在线人数统计
        try {
            RedisUtil.incr(WebSocketConstants.onlineCountKey, 1);
        } catch (Exception e) {
            WebSocketConstants.onlineCount++;
        }

    }

    public static synchronized void subOnlineCount() {
        // 优先redis 在线人数统计
        try {
            RedisUtil.decr(WebSocketConstants.onlineCountKey, 1);
        } catch (Exception e) {
            WebSocketConstants.onlineCount--;
        }
    }
}
